package jsonrpc2

/*test with curl
curl -X POST -H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"HelloService.Say","params":[{"Who":"Test"}], "id":"1"}' \
http://127.0.0.1:10000/
*/

import (
	"encoding/json"
	"io"
	"log"
	"net/http"
)

// RPCRequest jsonrpc standard request
type RPCRequest struct {
	Jsonrpc string      `json:"jsonrpc"`
	Method  string      `json:"method"`
	Params  interface{} `json:"Params"`
	ID      string      `json:"id"`
}

// RPCError jsonrpc standard error
type RPCError struct {
	Code    int32       `json:"code"`
	Message interface{} `json:"message"`
}

// RPC common errors
var (
	ParseError     = &RPCError{-32700, "Parse Error"}
	InvalidRequest = &RPCError{-32600, "Invalid Request"}
	MethodNotFound = &RPCError{-32601, "Method Not Found"}
	InvalidParams  = &RPCError{-32602, "Invalid Params"}
	InternalError  = &RPCError{-32603, "Internal Error"}
	CustomError    = &RPCError{-32000, "Custom Error"}
	// -32000 ~ 32099 or other code not mentioned here could be custom error
)

//RPCResponse is standard jsonrpc response
type RPCResponse struct {
	Jsonrpc string      `json:"jsonrpc"`
	Result  interface{} `json:"result"`
	Error   interface{} `json:"error"`
	ID      string      `json:"id"`
}

// the handler function
type handler func(params map[string]interface{}) (result interface{}, error interface{})

// save all methods registed
var methods = make(map[string]handler)

// RegisterFunc register functions
func RegisterFunc(method string, hlr handler) {
	methods[method] = hlr
}

func sendError(err *RPCError, req RPCRequest, w http.ResponseWriter) {
	res := &RPCResponse{
		Jsonrpc: req.Jsonrpc,
		Result:  nil,
		Error:   err,
		ID:      req.ID,
	}
	json, _ := json.Marshal(res)
	_, _ = io.WriteString(w, string(json))
}

func RPCHandler(w http.ResponseWriter, r *http.Request) {
	defer r.Body.Close()

	var req RPCRequest
	if e := json.NewDecoder(r.Body).Decode(&req); e != nil {
		sendError(ParseError, req, w)
		return
	}

	if req.Jsonrpc != string("2.0") || req.ID == "" {
		sendError(InvalidParams, req, w)
		return
	}

	if methods[req.Method] == nil {
		sendError(MethodNotFound, req, w)
		return
	}

	result, err := methods[req.Method](req.Params.(map[string]interface{}))

	res := &RPCResponse{
		Jsonrpc: req.Jsonrpc,
		Result:  result,
		Error:   err,
		ID:      req.ID,
	}
	json, _ := json.Marshal(res)
	_, _ = io.WriteString(w, string(json))
}

// StartServer Start the jsonrpc api
func StartServer(path string, port string) {
	http.HandleFunc(path, RPCHandler)
	log.Fatal(http.ListenAndServe(port, nil))
}
